#include "game.hpp"

GameScreen::GameScreen() 
{
	StageReset(1);
	StatReset();
}

GameScreen::~GameScreen()
{
}

void GameScreen::StatReset()
{
	lives = 0;
	score = 0;
	extscore = 30000;
}

void GameScreen::StageReset(int newstagen)
{
	state = PLAY;
	sttime = 0;

	stagen = newstagen;
	pause = false;
	ckpt = 0;
	stgtime = 0;
}

#include <iostream>
#include <cstdio>

#include "rand.hpp"

#include "sound.hpp"
#include "music.hpp"

#include "stage.hpp"
#include "cue.hpp"

#include "actor.hpp"
#include "player.hpp"
#include "enemy.hpp"
#include "weapon.hpp"
#include "item.hpp"
#include "door.hpp"
#include "boat.hpp"
#include "effect.hpp"

#include "camera.hpp"
#include "hud.hpp"

enum
{
	PAUSE,
	EXTEND,
	TIMECT,
	AMMOCT,
	SECRETHIT,
	EVENTCT
};

const char *EVENTSTRS[EVENTCT] = 
{
	"pause",
	"extend",
	"timect",
	"ammoct",
	"secret"
};

static Sound *snds[EVENTCT];

bool GameScreen::Open()
{
	boss = NULL;

	Rand.Seed();

	AllActs.Clear();
	
	bool success = false;

	char stgstr[5];
	std::sprintf(stgstr, "stg%1i", stagen);
	std::string stgname(stgstr);

	success = Stg.Open(stgname) 
		&& Enemy::Open()
		&& Item::Open()
		&& Weapon::Open()
		&& Effect::Open();

	if (success)
	{
		nextdoor = prevdoor = NULL;
		CamLimUpdate();

		stgname += "cue";

		Cues.Resize(Stg.Cols(), Stg.Rows());
		success = Cues.Open(stgname) && HUD.Open();

		if (success)
		{
			bool isplayer = false;

			for (int r = 0; r < Cues.Rows(); ++r)
			{
			for (int c = 0; c < Cues.Cols(); ++c)
			{
				Cue cue (Cues.Get(c, r));

				if (cue.Num() == Cue::PLAYER + ckpt)
				{
					isplayer = Plr.Open();
					if (isplayer)
					{
						Plr.Init(c, r, cue);
						AllActs.Add(&Plr);
					}
				}
				
				if (cue.IsEnemy())
					Enemy::TOpen(cue);/*
				if (Cue::IsItem(cue))
					Item::TOpen(cue);
				*/
				if (cue.IsDoor())
				{
					Door::Open();
					int doorn = cue.TypeN(Cue::DOOR0);
					Doors.Put(doorn, c, r, cue);
					if (doorn == ckpt)
						nextdoor = &Doors.Get(doorn);
					if (doorn + 1 == ckpt)
						prevdoor = &Doors.Get(doorn);
				}
			}
			}
			if (!isplayer)
			{
				std::cerr << "GameScreen::Open(): ";
				std::cerr << "No player in stage " << stagen;
				std::cerr << std::endl;
			}

			success = success && isplayer;
		}
	}

	if (success)
	{
		const int stgmus[] =
		{
			Mus.STG1,
			Mus.STG2,
			Mus.STG3,
			Mus.STG4,
			Mus.STG5,
			Mus.STG6,
			Mus.PREBOSSF
		};

		const int prebossmus[] =
		{
			Mus.PREBOSS2,
			Mus.PREBOSS2,
			Mus.PREBOSS2,
			Mus.PREBOSS2,
			Mus.PREBOSS2,
			Mus.PREBOSS6,
			Mus.PREBOSSF
		};

		Mus.Play(nextdoor ? stgmus[stagen-1] : prebossmus[stagen-1]);

		AllSnds.BatchGet(snds, EVENTCT, "game/", EVENTSTRS);

		HUD.Init();
		HUD.Put(HUD.NUM_LIVES, lives);
		HUD.Put(HUD.NUM_SCORE, score);
		HUD.Put(HUD.NUM_EXTSCORE, extscore);
		HUD.Put(HUD.GAUGE_BHP, 16);

		Plr.HUDUpdate();

		CamLimUpdate();

		stgtime = 18000 + (6000 * (stagen - ckpt));
		StateChange(PLAY);
	}
	else
		Close();

	return success;
};

#include "image.hpp"
#include "sound.hpp"

void GameScreen::Close()
{
	Stg.Close();
	Cues.Close();
	AllActs.Clear();
	AllImgs.Clear();
	AllSnds.Clear();
};

#include "input.hpp"
#include "app.hpp"
#include "title.hpp"

void GameScreen::In()
{
	if (Inp.CmdEdge(Inp.EXIT))
	{
		App.ScreenChange(&Title);
		return;
	}

	Vec2f joy (0, 0);
	int buttons = 0;

	if (state == DOOR)
	{
		joy.x = DoorScrolling() ? 0 : nextdoor->Face();
	}
	else if (state == PLAY)
	{
		joy = Vec2f(Inp.CmdOn(Inp.RIGHT) - Inp.CmdOn(Inp.LEFT),
			Inp.CmdOn(Inp.DOWN) - Inp.CmdOn(Inp.UP));

		buttons = Inp.EdgeBits();

		if (buttons & Inp.CmdBit(Inp.ENTER))
		{
			pause = !pause;

			if (pause)
			{
				snd = snds[PAUSE];
				Mus.Stop();
			}
			else
				Mus.Resume();
		}
	}
	
	if (!Plr.MeleeUpgrading())
	{
		Plr.In(joy, buttons);
	}
};

bool GameScreen::DoorScrolling()
{
	return state == DOOR && nextdoor->BackX() != Cam.AxCenter(X);
}

void GameScreen::ActScan()
{
	Vec2i secitempos[3];
	Vec2i scan (Cam.AxMin(X) - 2, Cam.AxMin(Y) - 2);
	Vec2i scan2 (Cam.AxMax(X) + 1, Cam.AxMax(Y) + 1);

	for (int r = scan.y; r <= scan2.y && r < Stg.Rows(); ++r)
	{
		if (r < 0)
			continue;

		for (int c = scan.x; c <= scan2.x && c < Stg.Cols(); ++c)
		{
			if (c < 0)
				continue;

			Cue cue (Cues.Get(c, r));

			if (cue.Num() == Cue::BOSSROOM)
			{
				if (boss && c - Cam.off.x <= .125f)
				{
					Mus.Play(Mus.BOSSREADY);
					StateChange(BOSSREADY);
					Cam.Pos(c, r);
					CamLimUpdate();
					Cues.Erase(c, r);
				}
				continue;
			}

			if (cue.IsEnemy())
			{
				if (state == PLAY && sttime > 0)
					if (Cam.Hitting(Vec2i(c, r)))
						continue;

				Enemy *e = Enmes.Init(c, r, cue);
				if (e && e->IsBoss())
				{
					boss = e;
				}
				continue;
			}

			if (cue.IsItem())
			{
				int itypen = cue.TypeN(Cue::ITEM);
				if (Item::POINT_XL <= itypen &&
					itypen <= Item::POINT_XXXL)
				{
					secitempos[itypen - Item::POINT_XL]
						= Vec2i(c, r);
				}
			}

			if (cue.IsBoat())
			{
				Boats.Init(c, r, cue);
				Cues.Erase(c, r);
			}
		}
	}

	Cue plrcue = Cues.Get(Plr.pos.x, Plr.pos.y);
	if (plrcue.IsSecret())
	{
		int secretn = plrcue.TypeN(Cue::SECRET_XL);
		Vec2i & ipos = secitempos[secretn];
		if (ipos.LenSq() > 0)
		{
			Cue icue (secretn + Cue::ITEM + Item::POINT_XL);
			Itms.Init(ipos.x, ipos.y, icue);
			Cues.Erase(Plr.pos.x, Plr.pos.y);
			snd = snds[SECRETHIT];
		}
	}
}

static const int DEADTIME = 240;

#include "select.hpp"

void GameScreen::ClearBonusUpdate()
{
	const int TIMETICK = 240;
	const int AMMOTICK = TIMETICK + 30;
	const int HPTICK = AMMOTICK + 60;
	const int HEALTICK = HPTICK + 1;
	const int DONETICK = HPTICK + 90;

	if (sttime >= DONETICK)
	{
		Select.StageClear(stagen);
		App.ScreenChange(&Select);
		return;
	}
	else if (sttime == HEALTICK)
	{
		if (Plr.hp == Plr.HPMAX)
			++sttime;
		else
			Plr.HPAdd(1);
	}
	else if (sttime == HPTICK)
	{
		if (Plr.hp == 0)
		{
			++sttime;
			snd = AllSnds.Get("item/heal");
		}
		else
		{
			Plr.HPAdd(-1);
			Score(250);
		}
	}
	else if (sttime == AMMOTICK)
	{
		if (Plr.ammo == 0)
			++sttime;
		else
		{
			Plr.AmmoAdd(-1);
			Score(100);
			snd = snds[AMMOCT];
		}
	}
	else if (sttime == TIMETICK)
	{
		if (stgtime <= 0)
			++sttime;
		else
		{
			stgtime -= 60;
			Score(10);
			snd = snds[TIMECT];
		}
	}
	else
	{
		++sttime;
	}
}

#include "gameover.hpp"

void GameScreen::Update()
{
	if (pause)
		return;

	if (state == PLAY)
	{
		Vec2f scanpos (Plr.LookPos(0));
		Cam.Pos(scanpos.x, scanpos.y);
		ActScan();
	}

	if (Plr.MeleeUpgrading())
		Plr.Update();
	else
	{
		AllActs.Update();

		Itms.Hit(Plr);
		Wpns.Hit(Enmes);
		Enmes.Hit(Plr);
		Doors.Hit(Plr);
	}

	AllActs.Bury();

	switch (state)
	{
	case CLEAR:
		ClearBonusUpdate();
		break;

	case DOOR:
		if (DoorScrolling())
		{
			Cam.Vel(nextdoor->Face() * .125f, 0);
			Cam.Update();
			if (!DoorScrolling())
				nextdoor->Shut(false);
		}
		else
		{
			Cam.Vel(0, 0);
			++sttime;
			if (sttime >= 64)
			{
				Cam.Vel(nextdoor->Face() * .125f, 0);
				Cam.Update();
				nextdoor->Shut(true);
			}
		}

		nextdoor->Update();

		if ((nextdoor->Face()>0 && nextdoor->FrontX()<=Cam.AxMin(X)) ||
			(nextdoor->Face()<0 && nextdoor->FrontX()>=Cam.AxMax(X)))
		{
			StateChange(PLAY);
			CkptUpdate();
			if (!nextdoor)
				Mus.Play(Mus.PREBOSS);
			CamLimUpdate();
		}

		break;

	case DEAD:
		if (sttime >= DEADTIME)
		{
			--lives;
			if (lives < 0)
			{
				App.ScreenChange(&GameOver);
				return;
			}

			if (!Open())
				App.Exit();

			return;
		}
		++sttime;
		break;

	case PLAY:
		if (!Plr.Live())
		{
			StateChange(DEAD);
			break;
		}

		if (boss && !boss->Live())
		{
			Mus.Stop();
		}

		if (nextdoor && nextdoor->Hitting(&Plr))
		{
			if (ckpt + 1 >= Door::COUNT 
				|| !Doors.Get(ckpt + 1).Exists())
			{
				Mus.Fade();
			}

			Enmes.Clear();
			Itms.Clear();
			Wpns.Clear();
			StateChange(DOOR);
			CamLimUpdate();
		}

		++sttime;
		--stgtime;
		break;
	
	case BOSSREADY:
		++sttime;
		if (sttime >= 300)
		{
			boss->Wake();
			Mus.Play(Mus.BOSS);
			StateChange(PLAY);
		}
		break;

	default:
		;
	}

	HUD.Put(HUD.NUM_LIVES, lives);
	HUD.Put(HUD.NUM_SCORE, score);
	HUD.Put(HUD.NUM_EXTSCORE, extscore);
	if (boss)
		boss->HUDUpdate();

	Plr.HUDUpdate();
	HUD.Update();
};

void GameScreen::CamLimUpdate()
{
	Vec2f min (0, 0);
	Vec2f max (Stg.Cols(), Stg.Rows());

	if (state == BOSSREADY)
	{
		min.x = Cam.AxMin(X);
		max.x = Cam.AxMax(X);
	}
	else if (state != DOOR)
	{
		if (nextdoor)
		{
			if (nextdoor->Face() < 0)
				min.x = nextdoor->FrontX();
			else
				max.x = nextdoor->FrontX();
		}
		if (prevdoor)
		{
			if (prevdoor->Face() < 0)
				max.x = prevdoor->FrontX();
			else
				min.x = prevdoor->FrontX();
		}
	}

	Cam.Min(min.x, min.y);
	Cam.Max(max.x, max.y);
}

void GameScreen::CkptUpdate()
{
	++ckpt;
	prevdoor = nextdoor;
	if (ckpt < Door::COUNT && Doors.Get(ckpt).Exists())
		nextdoor = &Doors.Get(ckpt);
	else 
		nextdoor = NULL;
}

#include "audio.hpp"

void GameScreen::Out(float upfrac)
{
	if (pause || Plr.MeleeUpgrading())
		upfrac = 0;

	switch (state)
	{
	case DOOR:
		Cam.Out(upfrac);
		break;
	
	default:
		Plr.LookAt(upfrac);
	}

	if (snd)
	{
		static int chan = -1;
		Aud.Pos(Cam.AxCenter(X), Cam.AxCenter(Y));
		chan = Aud.SoundPlay(*snd, chan);
		snd = NULL;
	}

	if (state == DEAD && sttime + 1 >= DEADTIME)
		return;

	Stg.Draw();
	AllActs.Out(upfrac);
	HUD.Out(upfrac);
}

void GameScreen::Score(int pts)
{
	score += pts;
	if (score >= extscore)
	{
		++lives;
		extscore += 50000;
		snd = snds[EXTEND];
	}
}

void GameScreen::StateChange(int s)
{
	if (state == s)
		return;

	switch (s)
	{
	case CLEAR:
		Mus.Play(Mus.CLEAR);
		break;

	default:
		;
	}

	state = s;
	sttime = 0;
}

GameScreen Game;
